<?php
/**
 * RedCross Application
 *
 * @copyright Copyright (c) 2010, Kristian Stokke Tryggestad, Rune Vikestad, Andreas Flaten Wist, Simen Nicolai Echholt, Ole Henrik Jahren
 */

/**
 * Zend_Auth adapter for Doctrine
 *
 * @category    RedCross
 * @package     RedCross_Auth
 */
class RedCross_Auth_Doctrine_Adapter implements Zend_Auth_Adapter_Interface
{
    /**
     * Database connection
     * @var Doctrine_Connection
     */
    protected $_connection;

    /**
     * The table name to use
     * @var string
     */
    protected $_tableName;

    /**
     * The column to use as the identity
     * @var string
     */
    protected $_identityColumn;

    /**
     * The column to use as the credential
     * @var string
     */
    protected $_credentialColumn;

    /**
     * The identity value
     * @var string
     */
    protected $_identity;

    /**
     * The credential value
     * @var string
     */
    protected $_credential;

    /**
     * If @a _credential is a cookie or not
     * @var boolean
     */
    protected $_cookie;

    /**
     * The authentication result information
     * @var array
     */
    protected $_authenticateResultInfo = null;

    /**
     * The results of the database authentication query
     * @var array
     */
    protected $_resultRow = null;

    /**
     * Constructor
     * Sets configuration options
     * @param Doctrine_Connection $connection
     * @param string $tableName
     * @param string $identityColumn
     * @param string $credentialColumn
     */
    public function __construct(Doctrine_Connection $connection = null, $tableName = null, $identityColumn = null, $credentialColumn = null)
    {
        if (null !== $connection) {
            $this->setConnection($connection);
        }

        if (null !== $tableName) {
            $this->setTableName($tableName);
        }

        if (null !== $identityColumn) {
            $this->setIdentityColumn($identityColumn);
        }

        if (null !== $credentialColumn) {
            $this->setCredentialColumn($credentialColumn);
        }
    }

    /**
     * Set the connection to the database
     * @param Doctrine_Connection $connection
     */
    public function setConnection(Doctrine_Connection $connection)
    {
        $this->_connection = $connection;
    }

    /**
     * Sets the table name to be used
     * @param string $tableName
     */
    public function setTableName($tableName)
    {
        $this->_tableName = $tableName;
    }

    /**
     * Sets the identity column
     * @param string $identityColumn
     */
    public function setIdentityColumn($identityColumn)
    {
        $this->_identityColumn = $identityColumn;
    }

    /**
     * Sets the credential column
     * @param string $credentialColumn
     */
    public function setCredentialColumn($credentialColumn)
    {
        $this->_credentialColumn = $credentialColumn;
    }

    /**
     * Get the connection to the database
     * @return Doctrine_Connection|null
     */
    public function getConnection()
    {
        if (null === $this->_connection && null !== $this->_tableName) {
            $this->_connection = Doctrine_Core::getConnectionByTableName($this->_tableName);
        }
        return $this->_connection;
    }

    /**
     * Set the identity
     * @param string $identity
     * @return RedCross_Auth_Doctrine_Adapter
     */
    public function setIdentity($identity)
    {
        $this->_identity = $identity;
        return $this;
    }

    /**
     * Set the credential
     * @param string $credential
     * @return RedCross_Auth_Doctrine_Adapter
     */
    public function setCredential($credential, $cookie)
    {
        $this->_credential = $credential;
	$this->_cookie = $cookie;
        return $this;
    }

    /**
     * Attempt an authentication. Prior to this call, the adapter should be configured with all
     * the necessary information to successfully connect to a database table to find a record matcing the provided identity.
     *
     * @throws Zend_Auth_Adapter_Exception if something goes wrong in the authentication process
     * @return Zend_Auth_Result
     */
    public function authenticate()
    {
        $this->_authenticateSetup();

        $select = $this->_authenticateCreateSelect();

        if ( ($authResult = $this->_authenticateValidateResultSet($select)) instanceof Zend_Auth_Result) {
            return $authResult;
        }

        $authResult = $this->_authenticateValidateResult(array_shift($select));
        return $authResult;
    }

    /**
     * Makes sure that the adapter was properly set up
     *
     * @throws Zend_Auth_Adapter_Exception if the adapter was not properly set up
     * @return true
     */
    protected function _authenticateSetup()
    {
        $exception = null;

        if ($this->getConnection() === null) {
            $exception = 'A database connection was not set, nor could one be created.';
        } elseif ($this->_tableName == '') {
            $exception = 'A table must be supplied for the authentication adapter.';
        } elseif ($this->_identityColumn == '') {
            $exception = 'An identity column must be supplied for the authentication adapter.';
        } elseif ($this->_credentialColumn == '') {
            $exception = 'A credential column must be supplied for the authentication adapter.';
        } elseif ($this->_identity == '') {
            $exception = 'A value for the identity was not provided prior to authentication.';
        } elseif ($this->_credential === null) {
            $exception = 'A credential value was not provided prior to authentication.';
        }

        if (null !== $exception) {
            throw new Zend_Auth_Adapter_Exception($exception);
        }

        $this->_authenticateResultInfo = array(
            'code'     => Zend_Auth_Result::FAILURE,
            'identity' => $this->_identity,
            'messages' => array()
        );

        return true;
    }

	/**
	 * Performs the database query
	 *
	 * @throws Zend_Auth_Adapter_Exception when an invalid select object is encountered
	 * @return array
	 */
	protected function _authenticateCreateSelect()
	{
		if ($this->_cookie)
		{
			$select = Doctrine_Query::create()
				->select()
				->from($this->_tableName . ' o')
				->leftJoin('o.Member m')
				->where("o.{$this->_identityColumn} = ?",
					$this->_identity)
				->andWhere("o.{$this->_credentialColumn} = ?",
					$this->_credential)
				->andWhere("o.{$this->_credentialColumn} IS NOT NULL");
		}
		else
		{
			$select = Doctrine_Query::create()
				->select()
				->from($this->_tableName . ' o')
				->leftJoin('o.Member m')
				->where("o.{$this->_identityColumn} = ?",
					$this->_identity)
				->andWhere("o.{$this->_credentialColumn} = SHA1(CONCAT(?, o.salt))",
					$this->_credential)
                ->andWhere("m.quit_date IS NULL");
		}

		try {
			$result = $select->execute()->toArray();
		} catch (Exception $e) {
			throw new Zend_Auth_Adapter_Exception('The supplied parameters failed to produce a valid sql statement,'
												. ' please check table and column names for validity.');
		}

		return $result;
	}

    /**
     * Makes sure that only one record was returned in the result set
     *
     * @param array $resultIdentities
     *  @return true|Zend_Auth_Result
     */
    protected function _authenticateValidateResultSet(array $resultIdentities)
    {
        if (count($resultIdentities) < 1) {
            $this->_authenticateResultInfo['code'] = Zend_Auth_Result::FAILURE_IDENTITY_NOT_FOUND;
            $this->_authenticateResultInfo['messages'][] = 'A record with the supplied identity could not be found.';
            return $this->_authenticateCreateAuthResult();
        } elseif (count($resultIdentities) > 1) {
            $this->_authenticateResultInfo['code'] = Zend_Auth_Result::FAILURE_IDENTITY_AMBIGUOUS;
            $this->_authenticateResultInfo['messages'][] = 'More than one record matches the supplied identity.';
            return $this->_authenticateCreateAuthResult();
        }
        return true;
    }

    /**
     * Makes sure that the record in the result set is valid
     *
     * @param array $resultIdentity
     * @return Zend_Auth_Result
     */
    protected function _authenticateValidateResult($resultIdentity)
    {
        if ($resultIdentity['is_confirmed'] != '1') {
            $this->_authenticateResultInfo['code'] = Zend_Auth_Result::FAILURE_UNCATEGORIZED;
            $this->_authenticateResultInfo['messages'][] = 'User is not activated or not confirmed.';
            return $this->_authenticateCreateAuthResult();
        }

        $this->_resultRow = $resultIdentity;

        $this->_authenticateResultInfo['code'] = Zend_Auth_Result::SUCCESS;
        $this->_authenticateResultInfo['messages'][] = 'Authentication successful.';
        return $this->_authenticateCreateAuthResult();
    }

    /**
     * Creates a Zend_Auth_Result object from the information that
     * has been collected during the authentication() attempt.
     *
     * @return Zend_Auth_Result
     */
    protected function _authenticateCreateAuthResult()
    {
        return new Zend_Auth_Result(
            $this->_authenticateResultInfo['code'],
            $this->_authenticateResultInfo['identity'],
            $this->_authenticateResultInfo['messages']
            );
    }

	/**
     * Returns the result row as a stdClass object
     *
     * @param  string|array $returnColumns
     * @param  string|array $omitColumns
     * @return stdClass|boolean
     */
    public function getResultRowObject($returnColumns = null, $omitColumns = null)
    {
        if (!$this->_resultRow) {
            return false;
        }

        $returnObject = new stdClass();

        if (null !== $returnColumns) {
            $availableColumns = array_keys($this->_resultRow);
            foreach ( (array) $returnColumns as $returnColumn) {
                if (in_array($returnColumn, $availableColumns)) {
                    $returnObject->{$returnColumn} = $this->_resultRow[$returnColumn];
                }
            }
            return $returnObject;

        } elseif (null !== $omitColumns) {
            $omitColumns = (array) $omitColumns;
            foreach ($this->_resultRow as $resultColumn => $resultValue) {
                if (!in_array($resultColumn, $omitColumns)) {
                    $returnObject->{$resultColumn} = $resultValue;
                }
            }
            return $returnObject;

        } else {

            foreach ($this->_resultRow as $resultColumn => $resultValue) {
                $returnObject->{$resultColumn} = $resultValue;
            }
            return $returnObject;

        }
    }
}
